package guitplugin;

import com.guit.client.apt.HasEntityProxy;
import com.guit.client.apt.HasValueProxy;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.Modifier;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationFilter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

public class ProxyProcessor implements AnnotationProcessor {

  AnnotationProcessorEnvironment env;
  private final Filer filer;

  public ProxyProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
    this.filer = env.getFiler();
  }

  public AnnotationProcessorEnvironment getEnvironment() {
    return env;
  }

  @Override
  public void process() {
    for (ClassDeclaration d : DeclarationFilter.getFilter(ClassDeclaration.class).filter(
        env.getDeclarationsAnnotatedWith((AnnotationTypeDeclaration) env
            .getTypeDeclaration(HasEntityProxy.class.getCanonicalName())), ClassDeclaration.class)) {
      createEntityProxy(d);
    }

    for (ClassDeclaration d : DeclarationFilter.getFilter(ClassDeclaration.class).filter(
        env.getDeclarationsAnnotatedWith((AnnotationTypeDeclaration) env
            .getTypeDeclaration(HasValueProxy.class.getCanonicalName())), ClassDeclaration.class)) {
      createValueProxy(d);
    }
  }

  private void createValueProxy(ClassDeclaration d) {
    // We asume we are on .shared.model, .client.model or .server.model
    String pack = d.getPackage().getQualifiedName();
    pack = pack.substring(0, pack.lastIndexOf("."));
    pack = pack.substring(0, pack.lastIndexOf("."));
    String clientPack = pack + ".client.model";

    String ownerName = d.getSimpleName();
    String name = ownerName + "Proxy";
    PrintWriter writer = getPrintWriter(clientPack + "." + name);
    writer.println("package " + clientPack + ";");
    writer.println();
    writer.println("import com.google.gwt.requestfactory.shared.ValueProxy;");
    writer.println("import com.google.gwt.requestfactory.shared.ProxyFor;");
    writer.println();
    writer.println("@ProxyFor(" + ownerName + ".class)");
    writer.println("public interface " + name + " extends EntityProxy {");
    for (MethodDeclaration m : d.getMethods()) {
      if (!m.getModifiers().contains(Modifier.PUBLIC)) {
        continue;
      }

      writer.println();
      writer.print("  " + AptUtils.getTypeQualifiedName(m.getReturnType(), true) + " "
          + m.getSimpleName() + "(");
      StringBuilder sb = new StringBuilder();
      for (ParameterDeclaration p : m.getParameters()) {
        if (sb.length() > 0) {
          sb.append(", ");
        }
        sb.append(AptUtils.getTypeQualifiedName(p.getType(), true) + " " + p.getSimpleName());
      }
      writer.append(sb.toString());
      writer.println(");");
    }
    writer.println("}");
    writer.close();
  }

  public static String getProxyForType(TypeDeclaration d) {
    if (d.getAnnotation(HasEntityProxy.class) != null
        || d.getAnnotation(HasValueProxy.class) != null) {
      String pack = d.getPackage().getQualifiedName();
      pack = pack.substring(0, pack.lastIndexOf("."));
      pack = pack.substring(0, pack.lastIndexOf("."));
      String clientPack = pack + ".client.model";

      String ownerName = d.getSimpleName();
      String name = ownerName + "Proxy";
      return clientPack + "." + name;
    } else {
      return d.getQualifiedName();
    }
  }

  private void createEntityProxy(ClassDeclaration d) {
    // Find service
    TypeDeclaration service = null;
    Collection<AnnotationMirror> mirrors = d.getAnnotationMirrors();
    for (AnnotationMirror mirror : mirrors) {
      String qName = mirror.getAnnotationType().getDeclaration().getQualifiedName();
      if (qName.equals(HasEntityProxy.class.getCanonicalName())) {
        Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror.getElementValues();
        for (Entry<AnnotationTypeElementDeclaration, AnnotationValue> e : values.entrySet()) {
          String vName = e.getKey().getSimpleName();
          if (vName.equals("service")) {
            service = (TypeDeclaration) e.getValue().getValue();
          }
        }
      }
    }

    if (service == null) {
      return;
    }

    // We asume we are on .shared.model, .client.model or .server.model
    String pack = d.getPackage().getQualifiedName();
    pack = pack.substring(0, pack.lastIndexOf("."));
    pack = pack.substring(0, pack.lastIndexOf("."));
    String clientPack = pack + ".client.model";

    String ownerName = d.getSimpleName();
    String name = ownerName + "Proxy";
    String proxyQualifiedName = clientPack + "." + name;
    PrintWriter writer = getPrintWriter(proxyQualifiedName);
    writer.println("package " + clientPack + ";");
    writer.println();
    writer.println("import com.google.gwt.requestfactory.shared.EntityProxy;");
    writer.println("import com.google.gwt.requestfactory.shared.ProxyFor;");
    writer.println();
    writer.println("import " + pack + ".server." + ownerName + "ServiceLocator;");
    writer.println("import " + d.getQualifiedName() + ";");
    writer.println();
    writer.println("@ProxyFor(value = " + ownerName + ".class, locator = " + ownerName
        + "ServiceLocator.class)");
    writer.println("public interface " + name + " extends EntityProxy {");
    for (MethodDeclaration m : d.getMethods()) {
      writer.println();

      if (!m.getModifiers().contains(Modifier.PUBLIC)) {
        continue;
      }

      writer.print("  " + AptUtils.getTypeQualifiedName(m.getReturnType(), true) + " "
          + m.getSimpleName() + "(");
      StringBuilder sb = new StringBuilder();
      for (ParameterDeclaration p : m.getParameters()) {
        if (sb.length() > 0) {
          sb.append(", ");
        }
        sb.append(AptUtils.getTypeQualifiedName(p.getType(), true) + " " + p.getSimpleName());
      }
      writer.append(sb.toString());
      writer.println(");");
    }
    writer.println("}");
    writer.close();

    // Create request factory
    name = ownerName + "RequestFactory";
    writer = getPrintWriter(pack + ".client." + name);
    writer.println("package " + pack + ".client;");
    writer.println();
    writer.println("import com.google.gwt.requestfactory.shared.RequestFactory;");
    writer.println();
    writer.println("public interface " + name + " extends RequestFactory {");
    writer.println("  " + ownerName + "Request request();");
    writer.println("}");
    writer.close();

    // Create request
    name = ownerName + "Request";
    writer = getPrintWriter(pack + ".client." + name);
    writer.println("package " + pack + ".client;");
    writer.println();
    writer.println("import com.google.gwt.requestfactory.shared.Request;");
    writer.println("import com.google.gwt.requestfactory.shared.RequestContext;");
    writer.println("import com.google.gwt.requestfactory.shared.Service;");
    writer.println();
    writer.println("import com.guit.server.requestfactory.GuitServiceLocator;");
    writer.println();
    writer.println("import " + pack + ".server." + ownerName + "ServiceLocator;");
    writer.println();
    writer.println("@Service(value = " + service.getSimpleName()
        + "Locator.class, locator = GuitServiceLocator.class)");
    writer.println("public interface " + name + " extends RequestContext {");

    for (MethodDeclaration m : service.getMethods()) {
      writer.println();

      if (!m.getModifiers().contains(Modifier.PUBLIC)) {
        continue;
      }

      writer.print("  Request<" + AptUtils.getProxyOrTypeQualifiedName(m.getReturnType(), false)
          + "> " + m.getSimpleName() + "(");
      StringBuilder sb = new StringBuilder();
      for (ParameterDeclaration p : m.getParameters()) {
        if (sb.length() > 0) {
          sb.append(", ");
        }
        sb.append(AptUtils.getProxyOrTypeQualifiedName(p.getType(), true) + " " + p.getSimpleName());
      }
      writer.append(sb.toString());
      writer.println(");");
    }
    writer.println("}");
    writer.close();

    // Creates locator
    name = service.getSimpleName() + "Locator";
    writer = getPrintWriter(pack + ".server." + name);
    writer.println("package " + pack + ".server;");
    writer.println();
    writer.println("import com.google.gwt.requestfactory.shared.Locator;");
    writer.println("import com.google.inject.Inject;");
    writer.println();
    writer.println("import " + d.getQualifiedName() + ";");
    writer.println();
    writer.println("public class " + name + " extends Locator<" + ownerName + ", Long> {");
    writer.println();
    writer.println("  @Inject");
    writer.println("  " + service.getQualifiedName() + " service;");
    for (MethodDeclaration m : service.getMethods()) {
      writer.println();

      if (!m.getModifiers().contains(Modifier.PUBLIC)) {
        continue;
      }

      writer.print("  public " + AptUtils.getTypeQualifiedName(m.getReturnType(), true) + " "
          + m.getSimpleName() + "(");
      StringBuilder sb = new StringBuilder();
      for (ParameterDeclaration p : m.getParameters()) {
        if (sb.length() > 0) {
          sb.append(", ");
        }
        sb.append(AptUtils.getTypeQualifiedName(p.getType(), true) + " " + p.getSimpleName());
      }
      writer.append(sb.toString());
      writer.println(") {");
      sb = new StringBuilder();
      for (ParameterDeclaration p : m.getParameters()) {
        if (sb.length() > 0) {
          sb.append(", ");
        }
        sb.append(p.getSimpleName());
      }
      GuitTypeVisitor v = new GuitTypeVisitor();
      m.getReturnType().accept(v);
      writer.print("    ");
      if (!v.isVoid()) {
        writer.print("return ");
      }
      writer.println("service." + m.getSimpleName() + "(" + sb.toString() + ");");
      writer.println("  }");
    }
    writer.println();
    writer.println("  @Override");
    writer.println("  public " + ownerName + " create(Class<? extends " + ownerName + "> clazz) {");
    writer.println("    return new " + ownerName + "();");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public Person find(Class<? extends " + ownerName + "> clazz, Long id) {");
    writer.println("    return service.find" + ownerName + "(id);");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public Class<" + ownerName + "> getDomainType() {");
    writer.println("    return " + ownerName + ".class;");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public Long getId(" + ownerName + " domainObject) {");
    writer.println("    return domainObject.getId();");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public Class<Long> getIdType() {");
    writer.println("    return Long.class;");
    writer.println("  }");
    writer.println();
    writer.println("  @Override");
    writer.println("  public Object getVersion(" + ownerName + " domainObject) {");
    writer.println("    return domainObject.getVersion();");
    writer.println("  }");
    writer.println("}");
    writer.close();
  }

  public PrintWriter getPrintWriter(String qualifiedName) {
    try {
      return filer.createSourceFile(qualifiedName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
}
